package com.sun.common;

import com.sun.entity.CrmLogMessage;
import com.sun.entity.User;
import com.sun.service.CrmLogMessageService;
import com.sun.service.UserService;
import com.sun.utils.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 * @Project: hospitalDrugInformationManagement
 * @Author: 86178
 * @Date: 2023/5/5 15:57
 * @FileName: SystemLogAspect
 * @Description: ""
 */
@Aspect
@Component
@Slf4j
public class SystemLogAspect {
    @Autowired
    private UserService userService;
    @Autowired
    private CrmLogMessageService crmLogMessageService;

    /**
     * 注解Pointcut切入点
     * 定义出一个或一组方法，当执行这些方法时可产生通知
     * 指向你的切面类方法
     * 由于这里使用了自定义注解所以指向你的自定义注解
     */
    @Pointcut("@annotation(com.sun.common.SystemCrmlog)")
    public void crmAspect() {
    }


    /**
     * 抛出异常后通知（@AfterThrowing）：方法抛出异常退出时执行的通知
     * 注意在这里不能使用ProceedingJoinPoint
     * 不然会报错ProceedingJoinPoint is only supported for around advice
     * throwing注解为错误信息
     *
     * @param joinPoint
     * @param ex
     */
    @AfterThrowing(value = "crmAspect()", throwing = "ex")
    public void afterThrowingMethod(JoinPoint joinPoint, Exception ex) throws Exception {
        HttpServletRequest httpServletRequest = getHttpServletRequest();
        //获取管理员用户信息
        Long id = BaseContext.getCurrentId();
        User user = userService.getById(id);
        CrmLogMessage log = new CrmLogMessage();
        //获取需要的信息
        String context = getServiceMthodDescription(joinPoint);
        String userName = "";
        String roleName = "";
        if (user != null) {
            userName = user.getNickName();
            roleName = user.getPosition();
        }
        //管理员姓名
        log.setNickName(userName);
        //角色名
        log.setUserRole(roleName);
        //日志信息
        log.setContent(userName + context);
        //设置参数集合
        log.setRemarks(getServiceMthodParams(joinPoint));
        //设置模块名
        log.setTitle(getServiceMthodTitle(joinPoint));
        //设置操作类型
        log.setType(getServiceMthodType(joinPoint));
        //操作时间
        SimpleDateFormat sif = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        log.setDateTime(sif.format(new Date()));
        //设置ip地址
        log.setIp(HttpUtils.getIpAddress(httpServletRequest));
        //设置请求地址
        log.setRequestUrl(httpServletRequest.getRequestURI());
        //执行结果
        log.setResult("执行失败");
        //错误信息
        log.setExString(ex.getMessage());
        //将数据保存到数据库
        crmLogMessageService.save(log);
    }


    /**
     * 返回后通知（@AfterReturning）：在某连接点（joinpoint）
     * 正常完成后执行的通知：例如，一个方法没有抛出任何异常，正常返回
     * 方法执行完毕之后
     * 注意在这里不能使用ProceedingJoinPoint
     * 不然会报错ProceedingJoinPoint is only supported for around advice
     * crmAspect()指向需要控制的方法
     * returning  注解返回值
     *
     * @param joinPoint
     * @param returnValue 返回值
     * @throws Exception
     */
    @AfterReturning(value = "crmAspect()", returning = "returnValue")
    public void doCrmLog(JoinPoint joinPoint, Object returnValue) throws Exception {
        HttpServletRequest httpServletRequest = getHttpServletRequest();
        //获取管理员用户信息
        Long id = BaseContext.getCurrentId();
        User user = userService.getById(id);
        CrmLogMessage log = new CrmLogMessage();
        //获取需要的信息
        String context = getServiceMthodDescription(joinPoint);
        String userName = "";
        String roleName = "";
        if (user != null) {
            userName = user.getNickName();
            roleName = user.getPosition();
        }

        //管理员姓名
        log.setNickName(userName);
        //角色名
        log.setUserRole(roleName);
        //日志信息
        log.setContent(userName + context);
        //设置参数集合
        log.setRemarks(getServiceMthodParams(joinPoint));
        //设置模块名
        log.setTitle(getServiceMthodTitle(joinPoint));
        //设置操作类型
        log.setType(getServiceMthodType(joinPoint));
        //操作时间
        SimpleDateFormat sif = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        log.setDateTime(sif.format(new Date()));
        //设置ip地址
        log.setIp(HttpUtils.getIpAddress(httpServletRequest));
        //设置请求地址
        log.setRequestUrl(httpServletRequest.getRequestURI());
        if (returnValue != null) {
            if (returnValue instanceof List) {
                List ls = (List) returnValue;
                if (ls.size() > 0) {
                    log.setResult("执行成功");
                } else {
                    log.setResult("执行失败");
                }
            } else if (returnValue instanceof Boolean) {
                Boolean falg = (Boolean) returnValue;
                if (falg) {
                    log.setResult("执行成功");
                } else {
                    log.setResult("执行失败");
                }
            } else if (returnValue instanceof Integer) {
                Integer i = (Integer) returnValue;
                if (i > 0) {
                    log.setResult("执行成功");
                } else {
                    log.setResult("执行失败");
                }
            } else {
                log.setResult("执行成功");
            }

        }
        //将数据保存到数据库
        crmLogMessageService.save(log);
    }

    /**
     * 获取自定义注解里的日志描述
     *
     * @param joinPoint
     * @return 返回注解里面的日志描述
     * @throws Exception
     */
    private String getServiceMthodDescription(JoinPoint joinPoint)
            throws Exception {
        //类名
        String targetName = joinPoint.getTarget().getClass().getName();
        //方法名
        String methodName = joinPoint.getSignature().getName();
        //参数
        Object[] arguments = joinPoint.getArgs();
        //通过反射获取示例对象
        Class targetClass = Class.forName(targetName);
        //通过实例对象方法数组
        Method[] methods = targetClass.getMethods();
        String description = "";
        for (Method method : methods) {
            //判断方法名是不是一样
            if (method.getName().equals(methodName)) {
                //对比参数数组的长度
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    //获取注解里的日志信息
                    description = method.getAnnotation(SystemCrmlog.class).description();
                    break;
                }
            }
        }
        return description;
    }

    /**
     * 获取自定义注解里的模块名
     *
     * @param joinPoint
     * @return 返回注解里的模块名
     * @throws Exception
     */
    private String getServiceMthodTitle(JoinPoint joinPoint)
            throws Exception {
        //类名
        String targetName = joinPoint.getTarget().getClass().getName();
        //方法名
        String methodName = joinPoint.getSignature().getName();
        //参数
        Object[] arguments = joinPoint.getArgs();
        //通过反射获取示例对象
        Class targetClass = Class.forName(targetName);
        //通过实例对象方法数组
        Method[] methods = targetClass.getMethods();
        //表名
        String title = "";
        for (Method method : methods) {
            //判断方法名是不是一样
            if (method.getName().equals(methodName)) {
                //对比参数数组的长度
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    //获取注解里的表名
                    title = method.getAnnotation(SystemCrmlog.class).title();
                    break;
                }
            }
        }
        return title;
    }

    /**
     * 获取自定义注解里的方法类型
     *
     * @param joinPoint
     * @return 返回注解里的方法类型
     * @throws Exception
     */
    private String getServiceMthodType(JoinPoint joinPoint)
            throws Exception {
        //类名
        String targetName = joinPoint.getTarget().getClass().getName();
        //方法名
        String methodName = joinPoint.getSignature().getName();
        //参数
        Object[] arguments = joinPoint.getArgs();
        //通过反射获取示例对象
        Class targetClass = Class.forName(targetName);
        //通过实例对象方法数组
        Method[] methods = targetClass.getMethods();
        //表名
        String type = "";
        for (Method method : methods) {
            //判断方法名是不是一样
            if (method.getName().equals(methodName)) {
                //对比参数数组的长度
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    //获取注解里的表名
                    type = method.getAnnotation(SystemCrmlog.class).type();
                    break;
                }
            }
        }
        return type;
    }

    /**
     * 获取json格式的参数用于存储到数据库中
     *
     * @param joinPoint
     * @return
     * @throws Exception
     */
    private String getServiceMthodParams(JoinPoint joinPoint)
            throws Exception {
        Object[] args = joinPoint.getArgs();
        String[] paramNames = ((MethodSignature) joinPoint.getSignature()).getParameterNames();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < args.length; i++) {
            sb.append(paramNames[i]).append("=").append(args[i]).append(", ");
        }
        String params = sb.toString();
        if (params.length() > 0) {
            params = params.substring(0, params.length() - 2);
        }
        return params;
    }

    /**
     * 获取当前的request
     * 这里如果报空指针异常是因为单独使用spring获取request
     * 需要在配置文件里添加监听
     * <listener>
     * <listener-class>
     * org.springframework.web.context.request.RequestContextListener
     * </listener-class>
     * </listener>
     *
     * @return
     */
    public HttpServletRequest getHttpServletRequest() {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        return request;
    }
}
